﻿#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using System.Collections.ObjectModel;
using System.Diagnostics;
#endregion

namespace GameBase.Sprites
{
    /// <summary>
    /// A sprite sheet contains many individual sprite images, packed into different
    /// areas of a single larger texture, along with information describing where in
    /// that texture each sprite is located.
    /// <para>Sprite sheets make drawing more efficient, because reduce the number of 
    /// times the graphics hardware needs to switch from one texture to another.</para>
    /// </summary>
    public class SpriteSheet : IDisposable
    {
        #region Fields

        // single texture containing all individual sprite images
        [ContentSerializer]
        Texture2D texture = null;

        // rectangles indicating where in the texture each sprite has been placed
        [ContentSerializer]
        List<Rectangle> spriteRectList = null;

        // store the original sprite filenames... look up sprites by name
        [ContentSerializer]
        Dictionary<string, int> spriteNameList = null;

        #endregion

        #region Properties

        /// <summary>
        /// Gets the single larger texture used by this sprite sheet.
        /// </summary>
        public Texture2D Texture
        {
            get { return texture; }
        }

        #endregion

        #region Look-Up

        /// <summary>
        /// Looks up the location of a sprite specified by its name within the larger texture.
        /// </summary>
        /// <param name="spriteName">Sprite name.</param>
        /// <returns>A rectangle that specifies the source texels from the larger texture.</returns>
        public Rectangle SourceRectangle(string spriteName)
        {
            int spriteIndex = GetIndex(spriteName);

            return spriteRectList[spriteIndex];
        }

        /// <summary>
        /// Looks up the location of a sprite specified by its index within the larger texture.
        /// </summary>
        /// <param name="spriteIndex">Sprite index.</param>
        /// <returns>A rectangle that specifies the source texels from the larger texture.</returns>
        public Rectangle SourceRectangle(int spriteIndex)
        {
            if ((spriteIndex < 0) || (spriteIndex >= spriteRectList.Count))
                throw new ArgumentOutOfRangeException("spriteIndex");

            return spriteRectList[spriteIndex];
        }

        /// <summary>
        /// Looks up the numeric index of a sprite specified by its name.
        /// <para>Useful when implementing animation by cycling through a series of related sprites.</para>
        /// </summary>
        /// <param name="spriteName">Sprite name.</param>
        /// <returns>Sprite index.</returns>
        public int GetIndex(string spriteName)
        {
            int index;

            if (!spriteNameList.TryGetValue(spriteName, out index))
            {
                throw new KeyNotFoundException("SpriteSheet does not contain sprite of name " + spriteName);
            }

            return index;
        }

        #endregion

        #region Dispose

        // dispose flag
        bool isDisposed = false;

        /// <summary>
        /// Immediately releases all resources used by this object.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Allows an object to try to free resources and perform other cleanup operations before it is reclaimed by garbage collection.
        /// </summary>
        ~SpriteSheet()
        {
            Dispose(false);
        }

        /// <summary>
        /// Releases the unmanaged resources used by this object and optionally releases the managed resources.
        /// </summary>
        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!isDisposed)
            {
                if (disposing)
                {
                    // cleanup managed resources
                    if (texture != null) { texture.Dispose(); texture = null; }
                }

                isDisposed = true;
            }            
        }

        #endregion
    }
}
